#include <string.h>
#include <stdio.h>
#include <kernel/shell.h>
#include <kernel/system.h>
#include <kernel/syslog.h>
#include <kernel/kobject.h>
#include <mm/mem.h>
#include <kernel/fdt.h>
#include <drivers/clock.h>


/**
 * pointer to current directory object
 */
static kobj_t *pwd_obj = NULL;


/**
 * @brief print current directory
 * @param kobj kobject
 * @return none
 */
static void kobj_print_current_dir(kobj_t *kobj)
{
    kobj_t *kobj_node;
    char kobj_name[64] = {0};

    struct list_node *head = &kobj->child;

    if(kobj_is_dir(kobj)) {
        list_for_each_entry(kobj_node, head, kobj_t, entry) {

            if(kobj_is_dir(kobj_node)) {
                sprintf(kobj_name, SHELL_COLOR_BLUE"%s"SHELL_COLOR_NONE, kobj_node->name);
            }
            else {
                sprintf(kobj_name, "%s", kobj_node->name);
            }

            uart_put_string(shell_device(), kobj_name);
            uart_put_string(shell_device(), "   ");
        }
    }

    uart_put_string(shell_device(), "\r\n");
}


/**
 * @brief show all command help information
 * @param argc arguement count
 * @param argv arguement array
 * @return int, no used
 */
static int help_cmd_exec(int argc, char **argv)
{
    (void)argc;
    (void)argv;
    char help_info[256] = {0};
    int pos = 0;

    shell_cmd_t *token_cmd = NULL;
    struct list_node *head = shell_get_list_head();

    list_for_each_entry(token_cmd, head, shell_cmd_t, node) {
        memset(help_info, ' ', sizeof(help_info));

        pos = sprintf(help_info, SHELL_COLOR_YELLOW"%s"SHELL_COLOR_NONE, token_cmd->name);
        help_info[pos] = ' ';
        sprintf(help_info + 20, "%s\r\n", token_cmd->desc);

        uart_put_string(shell_device(), help_info);
    }

    return 0;
}


struct shell_cmd help_cmd = {
    .name = "help",
    .desc = "show all command help information",
    .exec = help_cmd_exec,
};


static int exit_cmd_exec(int argc, char **argv)
{
    (void)argv;
    if(argc > 1) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }

    eb_sys_exit(0);

    return 0;
}


struct shell_cmd exit_cmd = {
    .name = "exit",
    .desc = "exit system",
    .exec = exit_cmd_exec,
};


static int ls_cmd_exec(int argc, char **argv)
{
    (void)argc;
    (void)argv;
    kobj_print_current_dir(pwd_obj);
    return 0;
}


struct shell_cmd ls_cmd = {
    .name = "ls",
    .desc = "show current directory all file and directory",
    .exec = ls_cmd_exec,
};


static int cd_cmd_exec(int argc, char **argv)
{
    const char *dir = argv[1];
    kobj_t* dir_obj = NULL;
    char out[256] = {0};

    if(argc == 1) {
        uart_put_string(shell_device(), "missing parameter\r\n");
    }
    else if(argc > 2) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }
    else {
        if(strcmp(dir, "../") == 0 || strcmp(dir, "..") == 0) {
            pwd_obj = pwd_obj->parent;
            return 0;
        }
        else if(strcmp(dir, "./") == 0 || strcmp(dir, ".") == 0) {
            return 0;
        }
        else if(strcmp(dir, "/") == 0) {
            pwd_obj = kobj_get_root();
            return 0;
        }

        dir_obj = kobj_find_by_name(pwd_obj, dir);
        if(dir_obj == NULL) {
            sprintf(out, "'%s' no such directory\r\n", dir);
            uart_put_string(shell_device(), out);
            return 0;
        }
        else {
            if(kobj_is_attr(dir_obj)) {
                sprintf(out, "'%s' is not directory\r\n", dir_obj->name);
                uart_put_string(shell_device(), out);
                return 0;
            }
        }

        pwd_obj = dir_obj;
    }

    return 0;
}


struct shell_cmd cd_cmd = {
    .name = "cd",
    .desc = "change current directory",
    .exec = cd_cmd_exec,
};


static int cat_cmd_exec(int argc, char **argv)
{
    const char *dir = argv[1];
    kobj_t* attr_obj = NULL;
    char out[256] = {0};

    if(argc == 1) {
        uart_put_string(shell_device(), "missing parameter\r\n");
    }
    else if(argc > 2) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }
    else {
        attr_obj = kobj_find_by_name(pwd_obj, dir);
        if(attr_obj == NULL) {
            uart_put_string(shell_device(), "no such file\r\n");
            return 0;
        }
        else {
            if(kobj_is_dir(attr_obj)) {
                sprintf(out, "'%s' is a directory\r\n", attr_obj->name);
                uart_put_string(shell_device(), out);
                return 0;
            }
        }

        uart_put_string(shell_device(), attr_obj->kdata->data);
    }

    return 0;
}


struct shell_cmd cat_cmd = {
    .name = "cat",
    .desc = "cat data of file",
    .exec = cat_cmd_exec,
};


static int pwd_cmd_exec(int argc, char **argv)
{
    (void)argv;
    kobj_t* kobj = pwd_obj;
    kobj_t* stack[128] = {0};
    int stack_p = 0;
    char out[128] = {0};

    if(argc > 1) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }
    else {
        do {
            stack[stack_p++] = kobj;
            kobj = kobj->parent;
        } while(kobj != kobj_get_root());

        for(int i = stack_p - 1; i >= 0; i --) {
            sprintf(out, "/%s", stack[i]->name);
            uart_put_string(shell_device(), out);
        }

        uart_put_string(shell_device(), "\r\n");
    }

    return 0;
}


struct shell_cmd pwd_cmd = {
    .name = "pwd",
    .desc = "show current path",
    .exec = pwd_cmd_exec,
};


static int free_cmd_exec(int argc, char **argv)
{
    (void)argv;
    char out[512] = {0};
    int i = 0;

    memset(out, 0, sizeof(out));
    
    if(argc > 1) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }
    else {
        kmem_monitor_t monitor;
        monitor = kmem_get_monitor();
        i = sprintf(out, "Memory Information - Reference Only\r\n");
        i += sprintf(out + i, "total     : %d Bytes\r\n", monitor.total_size);
        i += sprintf(out + i, "used      : %d Bytes\r\n", monitor.used_size);
        i += sprintf(out + i, "free      : %d Bytes\r\n", monitor.free_size);

        int integer, decimal;
        integer = (monitor.used_rate >> 8) & 0xff;
        decimal = monitor.used_rate & 0xff;
        i += sprintf(out + i, "used rate : %d.%d %%\r\n", integer, decimal);

        uart_put_string(shell_device(), out);
    }

    return 0;
}


struct shell_cmd lsmem_cmd = {
    .name = "free",
    .desc = "show memory information",
    .exec = free_cmd_exec,
};


static int clear_cmd_exec(int argc, char **argv)
{
    (void)argv;
    if(argc != 1) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }
    else {
        uart_put_string(shell_device(), "\033[2J\033[H");
    }
    return 0;
}


struct shell_cmd clear_cmd = {
    .name = "clear",
    .desc = "clean display",
    .exec = clear_cmd_exec,
};


static int clock_cmd_exec(int argc, char **argv)
{
    (void)argv;
    uint8_t buffer[128];

    if(argc != 1) {
        uart_put_string(shell_device(), "too many parameter\r\n");
    }
    else {
        fdt_node_t *node = NULL;
        fdt_node_t *sub_node = NULL;
        fdt_node_t *clock = fdt_find_node_by_path("/clock");
        const char *clock_name = NULL;
        size_t freq = 0;

        fdt_for_each_node_child(clock, node) {
            memset(buffer, ' ', sizeof(buffer));
            clock_name = node->name;

            int ret = fdt_read_prop_int(node, "freq", &freq);
            if(ret) {
                continue;
            }

            int len = sprintf(buffer, "%s", clock_name);
            buffer[len] = ' ';
            sprintf(buffer + 20, "%d Hz\r\n", freq);
            uart_put_string(shell_device(), buffer);
        }
    }
    return 0;
}


struct shell_cmd clock_cmd = {
    .name = "clock",
    .desc = "show clock information",
    .exec = clock_cmd_exec,
};


static int cmd_base_register(void)
{
    pwd_obj = kobj_get_root();

    if(shell_cmd_register(&help_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&exit_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&ls_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&cd_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&cat_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&pwd_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&lsmem_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&clear_cmd)) {
        return -1;
    }

    if(shell_cmd_register(&clock_cmd)) {
        return -1;
    }

    return 0;
}


module_initcall(cmd_base_register);
